#include <SpuProgram.h>
#include <csp_debug.h>
#include <csp_macros.h>
#include <csp_shared_types.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <libspe2.h>
#include <pthread.h>
#include <malloc_align.h>
#include <string>
#include <elf.h>

using namespace std;

SpuProgram::SpuProgram(string path_, string mnemonic_)
{
	mnemonic = new char [mnemonic_.size()+1];
	strcpy(mnemonic, mnemonic_.c_str());

	path = new char[path_.size()+1];
	strcpy(path, path_.c_str());

	char suffix[] = "_?";
	char *tmp_filename = (char*) malloc(strlen(path) + strlen(suffix)+1);


	for(int i = 0; i < MAX_SPU_SLOTS; i++){
		suffix[1] = '0' + i;

		strcpy(tmp_filename, path);
		strcat(tmp_filename, suffix);

		read_spu_elf(tmp_filename, i);
	}
	next_slot = 0;
}

SpuProgram::~SpuProgram()
{
}




void 
SpuProgram::CopyToProcess(pcb_t *proc, unsigned char ls_mem_slot)
{
	/*
	   proc->exec      = _exec;
	   proc->exec_size = _exec_size;
	   proc->bss_size  = _bss_size;
	   proc->vaddr     = _vaddr;
	   proc->entry     = _entry;
	   proc->stack_start = _stack_start;
	   if(NULL == memcpy(proc, &init_data[ls_mem_slot],sizeof(pcb_t))){
	   perror("Could not copy spu-program code.");
	   }

*/
	proc->exec        = init_data[ls_mem_slot].exec;
	proc->exec_size   = init_data[ls_mem_slot].exec_size;
	proc->bss_size    = init_data[ls_mem_slot].bss_size;
	proc->vaddr       = init_data[ls_mem_slot].vaddr;
	proc->entry       = init_data[ls_mem_slot].entry;
	proc->stack_start = init_data[ls_mem_slot].stack_start;
}

	uint32_t 
SpuProgram::_run_symtab(Elf32_Ehdr *ehdr, Elf32_Shdr *sh, char *sym_needle)
{

	Elf32_Sym  *sym, *sym_hdr, *sym_end;
	Elf32_Shdr *shdr;
	char *str_table;
	char *sym_name;

	shdr = (Elf32_Shdr*) ((char*) ehdr + ehdr->e_shoff);
	sym_hdr = (Elf32_Sym*) ((char*) ehdr + sh->sh_offset);
	sym_end = (Elf32_Sym*) ((char*) ehdr + sh->sh_offset + sh->sh_size);
	str_table = (char*)ehdr + shdr[sh->sh_link].sh_offset;

	/* Iterate through symbol table */
	for (sym = sym_hdr; sym < sym_end; sym++)
		if (sym->st_name) {
			sym_name = str_table + sym->st_name;
			//printf("%s: size=%x, value=%x\n", sym_name, sym->st_size, sym->st_value);
			if( strcmp(sym_name, sym_needle) == 0 ){
				//printf("yes\n");
				/* hack: we ignore data objects */
				if( !sym->st_size ){
					return sym->st_value;	
				}
			}
		}
	return 0;
}


	uint32_t 
SpuProgram::find_symbol(void *elf_image, char *symbol)
{
	Elf32_Ehdr *ehdr;
	Elf32_Shdr *shdr, *n;
	char *str_table;
	char *section_name;
	int i;

	ehdr = (Elf32_Ehdr *) elf_image;
	/* section header table */
	shdr = (Elf32_Shdr*) ((char*) ehdr + ehdr->e_shoff);
	str_table = (char*)ehdr + shdr[ehdr->e_shstrndx].sh_offset;

	/* Iterate through section headers */
	for(i=0; i < ehdr->e_shnum; i++){
		n = shdr + i;
		section_name = str_table + n->sh_name;
		//printf("Section name: %s.\n", section_name);
		if( strncmp(".symtab", section_name, 7) == 0 ){
			if(n->sh_type == SHT_SYMTAB || n->sh_type == SHT_DYNSYM){
				return _run_symtab(ehdr, n, symbol);
			}
		}
	}
	return 0;
}


	void 
SpuProgram::read_spu_elf(char *filename, int slot)
{

	spe_program_handle_t *program;
	void *elf_image;
	Elf32_Ehdr *ehdr;
	Elf32_Phdr *phdr;


	//program = spe_image_open(filename);
	program = spe_image_open(filename);
	ASSERT(program);
	elf_image = program->elf_image;

	ASSERT(elf_image);

	/* elf- and program header */
	ehdr = (Elf32_Ehdr *) elf_image;
	phdr = (Elf32_Phdr *) ((ea_addr_t)ehdr + (ea_addr_t)ehdr->e_phoff);

	/* Check that segment is loadable */
	ASSERT(phdr->p_type == PT_LOAD);
	//ASSERT(phdr->p_vaddr == CSP_PROCESS1_VADDR);

	/*
	   _exec        = (uintptr_t)((ea_addr_t)ehdr + (ea_addr_t)phdr->p_offset);
	   _exec_size   = phdr->p_filesz;
	   _bss_size    = phdr->p_memsz - phdr->p_filesz;
	   _vaddr       = phdr->p_vaddr;
	   _entry       = ehdr->e_entry;
	   _stack_start = find_symbol(elf_image, "__stack");
	   */

	init_data[slot].exec        = (uintptr_t)((ea_addr_t)ehdr + (ea_addr_t)phdr->p_offset);
	init_data[slot].exec_size   = phdr->p_filesz;
	init_data[slot].bss_size    = phdr->p_memsz - phdr->p_filesz;
	init_data[slot].vaddr       = phdr->p_vaddr;
	init_data[slot].entry       = ehdr->e_entry;
	init_data[slot].stack_start = find_symbol(elf_image, "__stack");

	ASSERT( init_data[slot].exec        ); 
	ASSERT( init_data[slot].vaddr       );
	ASSERT( init_data[slot].entry       );
	ASSERT( init_data[slot].stack_start );

	//ASSERT(_stack_start);
}


void 
SpuProgram::read_spu_raw_binary(char *filename){
	DEBUG_PRINTF("Opening file: %s.\n", filename);
	/* declare a file pointer */
	FILE    *infile;
	ls_size_t prgm_size;

	/* open an existing file for reading */
	infile = fopen(filename, "r");

	/* quit if the file does not exist */
	if(infile == NULL){
		perror("Cannot open spu--elf");
		exit(1);
	}

	/* Get the number of bytes */
	fseek(infile, 0L, SEEK_END);
	prgm_size = (ls_size_t) ftell(infile);

	/* Make sure size is a multiple of 16 */
	size_ = CEIL_16(prgm_size); 
	if(size_ != prgm_size){
		DEBUG_PRINTF("Programsize of (%i) bytes was rounded up to a multiple of 16: %i bytes", prgm_size, size_);
	}


	/* reset the file position indicator to 
	   the beginning of the file */
	fseek(infile, 0L, SEEK_SET);

	/* grab sufficient memory for the 
	   buffer to hold the text */
	img_ = (ea_addr_t) _malloc_align(size_ * sizeof(char), 7);
	//buffer = (char*)calloc(numbytes, sizeof(char));	

	/* memory error */
	if(NULL == (void*) img_){
		perror("No memory?");
		exit(1);
	}

	/* copy into the buffer */
	fread((void *) img_, sizeof(char), prgm_size, infile);
	fclose(infile);
	DEBUG_PRINTF("Program loaded: %s.\n", filename);
}
